<!DOCTYPE html>
<HTML>
<HEAD><meta name="viewport" content="width=device-width, initial-scale=1">
<Title>Zoom/Scroll with PDF Report</Title>
<link type='text/css' rel='Stylesheet' href="maxchartapi.css" />
</HEAD>
<body bgcolor="#FFFFFF" text="#000000" topmargin="0" leftmargin="0" rightmargin="0" marginwidth="0" marginheight="0">
<p class="heading0">ChartDirector 7.0 (Java Edition)</p>
<p class="heading1">Zoom/Scroll with PDF Report</p>
<hr class="separator">
<div class="content"><img src="images/zoomscrollpdf_web.png" width='793' height='468'><br><br>
This example is modified from <a href="zoomscrolltrackweb.htm">Zooming and Scrolling with Track Line</a> to demonstrate creating a PDF report with multiple charts covering the entire data range.<br><br>
This example adds two buttons to the web page to download the visible chart image and the complete PDF report. These buttons use <a href="JsChartViewer.partialUpdateAsAttachment.htm">JsChartViewer.partialUpdateAsAttachment</a> to generate the download attachment requests.<br><br>
The code on the server side needs to handle 4 kinds of requests:<br><br>
<ul> <li> Initial request: The server will return the complete web page with the chart.<br><br>
<li> Partial update request: This occurs when the chart is zoomed in/out of scrolled. They are generated using <a href="JsChartViewer.partialUpdate.htm">JsChartViewer.partialUpdate</a> on the browser side. The server returns the updated chart and track cursor using <a href="WebChartViewer.partialUpdateChart.htm">WebChartViewer.partialUpdateChart</a>.<br><br>
<li> Download chart image request: This is generated on the browser side using <a href="JsChartViewer.partialUpdateAsAttachment.htm">JsChartViewer.partialUpdateAsAttachment</a>. The server will use <a href="WebChartViewer.streamChart.htm">WebChartViewer.streamChart</a> to send the chart image to the browser, with a flag set in the HTTP header to indicate it is an attachment. The browser should then save the chart image as a file download.<br><br>
<li> Download PDF report request: This is generated on the browser side using <a href="JsChartViewer.partialUpdateAsAttachment.htm">JsChartViewer.partialUpdateAsAttachment</a>. A flag is used to indicate that it is for the PDF report. The server will use a <a href="MultiPagePDF.htm">MultiPagePDF</a> object to create the PDF report, and use <a href="WebChartViewer.streamChart.htm">WebChartViewer.streamChart</a> to send the report to the browser, with a flag set in the HTTP header to indicate it is an attachment<br></ul>
MultiPagePDF works by generating a PDF page for chart added to it. The chart can be a <a href="MultiChart.htm">MultiChart</a>, and can contain free form CDML text, tables, shape and images. In this way, MultiPagePDF can create complete PDF reports.<br><br>
In this example, the PDF report is generated as follows:<br><br>
<ul>
<li>A new <a href="MultiPagePDF.htm">MultiPagePDF</a> object is created.<br><br>
<li>An empty <a href="PieChart.htm">PieChart</a> object with custom CDML text in large bold font is added to the MultiPagePDF as the cover page. The PieChart is configured with the output options "pagewidth = 794; pageHeight = 1123" using <a href="BaseChart.setOutputOptions.htm">BaseChart.setOutputOptions</a>. As PDF defaults to 96dpi (dot per inch), the above sets the page to A4 size (210mm x 297mm).<br><br>
<li>A loop is used to generate <a href="MultiChart.htm">MultiChart</a> objects as pages in the MultiPagePDF. Each MultiChart object will include a page heading at the top, and contain up to two <a href="XYChart.htm">XYChart</a> objects, and with a page number at the bottom. Each XYChart object will plot one year of data. Like the cover page, the page is set to A4 size.<br></ul>
</div><p class="heading1a">Source Code Listing</p><div class="content">
<b>[JSP Version]</b> jspdemo/zoomscrollpdf.jsp
<div class='codeblock'><code class='pre'>&lt;%@page import="ChartDirector.*, java.util.*" %&gt;
&lt;%!
//
// Create a random table for demo purpose.
//
private RanTable getRanTable()
{
    RanTable r = new RanTable(127, 4, 1827);
    r.setDateCol(0, new GregorianCalendar(2015, 0, 1).getTime(), 86400);
    r.setCol(1, 150, -10, 10);
    r.setCol(2, 200, -10, 10);
    r.setCol(3, 250, -8, 8);
    return r;
}

//
// Initialize the WebChartViewer when the page is first loaded
//
private void initViewer(WebChartViewer viewer)
{
    // The full x-axis range is from Jan 1, 2007 to Jan 1, 2012
    Date startDate = new GregorianCalendar(2015, 0, 1).getTime();
    Date endDate = new GregorianCalendar(2020, 0, 1).getTime();
    viewer.setFullRange("x", startDate, endDate);

    // Initialize the view port to show the last 366 days (out of 1827 days)
    viewer.setViewPortWidth(366.0 / 1827);
    viewer.setViewPortLeft(1 - viewer.getViewPortWidth());

    // Set the maximum zoom to 10 days (out of 1827 days)
    viewer.setZoomInWidthLimit(10.0 / 1827);
}

//
// Draw an XYChart using data from startX to endX
//
private XYChart drawXYChart(Date startX, Date endX)
{
    // Obtain the random data table
    RanTable r = getRanTable();

    // Select the data for the visible date range startDate to endDate. It is possible there is no
    // data point at exactly startDate or endDate. In this case, we also need the data points that
    // are just outside the visible date range to "overdraw" the line a little bit (the "overdrawn"
    // part will be clipped to the plot area) In this demo, we do this by adding a one day margin to
    // the date range when selecting the data. The selected data from the random data table
    Date[] timeStamps = Chart.NTime(r.getCol(0));
    double[] dataSeriesA = r.getCol(1);
    double[] dataSeriesB = r.getCol(2);
    double[] dataSeriesC = r.getCol(3);

    //
    // At this stage, we have extracted the visible data. We can use those data to plot the chart.
    //

    //================================================================================
    // Configure overall chart appearance.
    //================================================================================

    // Create an XYChart object of size 640 x 350 pixels
    XYChart c = new XYChart(640, 350);

    // Set the plotarea at (55, 50) with width 80 pixels less than chart width, and height 85 pixels
    // less than chart height. Use a vertical gradient from light blue (f0f6ff) to sky blue (a0c0ff)
    // as background. Set border to transparent and grid lines to white (ffffff).
    c.setPlotArea(55, 50, c.getWidth() - 80, c.getHeight() - 85, c.linearGradientColor(0, 50, 0,
        c.getHeight() - 35, 0xf0f6ff, 0xa0c0ff), -1, Chart.Transparent, 0xffffff, 0xffffff);

    // As the data can lie outside the plotarea in a zoomed chart, we need enable clipping.
    c.setClipping();

    // Add a legend box at (55, 25) using horizontal layout. Use 8pts Arial Bold as font. Set the
    // background and border color to Transparent and use line style legend key.
    LegendBox b = c.addLegend(55, 25, false, "Arial Bold", 8);
    b.setBackground(Chart.Transparent);
    b.setLineStyleKey();

    // Set the axis stem to transparent
    c.xAxis().setColors(Chart.Transparent);
    c.yAxis().setColors(Chart.Transparent);

    // Add axis title using 10pts Arial Bold Italic font
    c.yAxis().setTitle("Ionic Temperature (C)", "Arial Bold Italic", 10);

    //================================================================================
    // Add data to chart
    //================================================================================

    //
    // In this example, we represent the data by lines. You may modify the code below to use other
    // representations (areas, scatter plot, etc).
    //

    // Add a line layer for the lines, using a line width of 2 pixels
    LineLayer layer = c.addLineLayer2();
    layer.setLineWidth(2);

    // In this demo, we do not have too many data points. In real code, the chart may contain a lot
    // of data points when fully zoomed out - much more than the number of horizontal pixels in this
    // plot area. So it is a good idea to use fast line mode.
    layer.setFastLineMode();

    // Now we add the 3 data series to a line layer, using the color red (ff33333), green (008800)
    // and blue (3333cc)
    layer.setXData(timeStamps);
    layer.addDataSet(dataSeriesA, 0xff3333, "Alpha");
    layer.addDataSet(dataSeriesB, 0x008800, "Beta");
    layer.addDataSet(dataSeriesC, 0x3333cc, "Gamma");

    //================================================================================
    // Configure axis scale and labelling
    //================================================================================

    // Set the x-axis scale
    c.xAxis().setDateScale(startX, endX);

    //
    // In this demo, the time range can be from a few years to a few days. We demonstrate how to set
    // up different date/time format based on the time range.
    //

    // If all ticks are yearly aligned, then we use "yyyy" as the label format.
    c.xAxis().setFormatCondition("align", 360 * 86400);
    c.xAxis().setLabelFormat("{value|yyyy}");

    // If all ticks are monthly aligned, then we use "mmm yyyy" in bold font as the first label of a
    // year, and "mmm" for other labels.
    c.xAxis().setFormatCondition("align", 30 * 86400);
    c.xAxis().setMultiFormat(Chart.StartOfYearFilter(), "&lt;*font=bold*&gt;{value|mmm yyyy}",
        Chart.AllPassFilter(), "{value|mmm}");

    // If all ticks are daily algined, then we use "mmm dd&lt;*br*&gt;yyyy" in bold font as the first
    // label of a year, and "mmm dd" in bold font as the first label of a month, and "dd" for other
    // labels.
    c.xAxis().setFormatCondition("align", 86400);
    c.xAxis().setMultiFormat(Chart.StartOfYearFilter(),
        "&lt;*block,halign=left*&gt;&lt;*font=bold*&gt;{value|mmm dd&lt;*br*&gt;yyyy}", Chart.StartOfMonthFilter(),
        "&lt;*font=bold*&gt;{value|mmm dd}");
    c.xAxis().setMultiFormat2(Chart.AllPassFilter(), "{value|dd}");

    // For all other cases (sub-daily ticks), use "hh:nn&lt;*br*&gt;mmm dd" for the first label of a day,
    // and "hh:nn" for other labels.
    c.xAxis().setFormatCondition("else");
    c.xAxis().setMultiFormat(Chart.StartOfDayFilter(), "&lt;*font=bold*&gt;{value|hh:nn&lt;*br*&gt;mmm dd}",
        Chart.AllPassFilter(), "{value|hh:nn}");

    return c;
}

//
// Draw the chart
//
private void drawChart(WebChartViewer viewer)
{
    // Determine the visible x-axis range
    Date viewPortStartDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.getViewPortLeft()));
    Date viewPortEndDate = Chart.NTime(viewer.getValueAtViewPort("x", viewer.getViewPortRight()));

    // Draw the XYChart
    XYChart c = drawXYChart(viewPortStartDate, viewPortEndDate);

    // Add a title to the chart using 18 pts Times New Roman Bold Italic font
    c.addTitle("   PDF Report Demonstration", "Times New Roman Bold Italic", 18);

    if (viewer.isAttachmentRequest()) {
        // Output as PNG attachment
        viewer.setChart(c, Chart.PNG);
    } else {
        // Output the chart
        viewer.setChart(c, Chart.SVG);

        // Output Javascript chart model to the browser to support tracking cursor
        viewer.setChartModel(c.getJsChartModel());
    }
}

//
// Create a multi-page PDF Report
//
private byte[] createPdfReport(WebChartViewer viewer)
{
    // The MultiPagePDF object can create PDF from multiple pages, each with one chart object. Since
    // a chart object can contain text (eg. using BaseChart.addText) and other charts (eg. using
    // MultiChart), that means each page can contain text and multiple charts.
    MultiPagePDF doc = new MultiPagePDF();

    // Page configuration - A4 = 210 x 297mm. The PDF default is 96 dpi (dot per inch), so the A4
    // size is equal to 794 x 1123 dots.
    String pageConfig = "pagewidth = 794; pageheight = 1123";

    // In this example, we include a cover page with only text. This is by creating an empty pie
    // chart with text only.
    PieChart firstPage = new PieChart(720, 960);
    firstPage.addText(360, 320,
        "&lt;*size=50*&gt;ChartDirector&lt;*br*&gt;&lt;*size=30*&gt;PDF Report Demonstration&lt;*/*&gt;", "Arial Bold", 30,
        0x000000, Chart.Center);
    firstPage.setOutputOptions(pageConfig);
    doc.addPage(firstPage);

    // We include 2 charts per page, with each chart showing one year of data. Each page will also
    // have a header and page number
    int startYear = Chart.JCalendar(viewer.getValueAtViewPort("x", 0)).get(Calendar.YEAR);
    int endYear = Chart.JCalendar(viewer.getValueAtViewPort("x", 1) - 1).get(Calendar.YEAR);
    int pageNumber = 0;

    for(int yyyy = startYear; yyyy &lt; endYear + 1; yyyy += 2) {
        // This chart is the page.
        MultiChart m = new MultiChart(760, 920);

        // Use addTitle to add a header
        m.addTitle("ChartDirector PDF Report Demonstration", "Arial Bold", 20);

        // Create the first chart
        XYChart c = drawXYChart(new GregorianCalendar(yyyy, 0, 1).getTime(), new GregorianCalendar(
            yyyy + 1, 0, 1).getTime());
        m.addChart((m.getWidth() - c.getWidth()) / 2, 100, c);
        c.addTitle("Year " + yyyy);

        XYChart c2 = null;
        if (yyyy &lt; endYear) {
            // Create the second chart
            c2 = drawXYChart(new GregorianCalendar(yyyy + 1, 0, 1).getTime(), new GregorianCalendar(
                yyyy + 2, 0, 1).getTime());
            c2.addTitle("Year " + (yyyy + 1));
            m.addChart((m.getWidth() - c2.getWidth()) / 2, 500, c2);
        }

        // Add the page number
        pageNumber = pageNumber + 1;
        m.addTitle(Chart.BottomCenter, String.valueOf(pageNumber), "Arial Bold", 8);

        m.setOutputOptions(pageConfig);
        doc.addPage(m);
    }

    // Output the PDF report
    return doc.outPDF2();
}
%&gt;
&lt;%

// Create the WebChartViewer object
WebChartViewer viewer = new WebChartViewer(request, "chart1");

if (viewer.isPartialUpdateRequest()) {
    // Is a partial update request.
    out.clear();

    if ("report".equals(request.getParameter("download"))) {
        // Download PDF report
        viewer.streamChart(response, createPdfReport(viewer), "chartdirector_report.pdf", true);
        return;
    }

    // Draw the chart and perform a partial response.
    drawChart(viewer);
    viewer.partialUpdateChart(response);
    return;
}

//
// If the code reaches here, it is a full page request.
//

// In this exapmle, we just need to initialize the WebChartViewer and draw the chart.
initViewer(viewer);
drawChart(viewer);
%&gt;
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;PDF Report Demonstration&lt;/title&gt;
    &lt;script type="text/javascript" src="cdjcv.js"&gt;&lt;/script&gt;
    &lt;style type="text/css"&gt;
        .chartButton { font:12px Verdana; border-bottom:#000000 1px solid; padding:5px; cursor:pointer;}
        .chartButtonSpacer { font:12px Verdana; border-bottom:#000000 1px solid; padding:5px;}
        .chartButton:hover { box-shadow:inset 0px 0px 0px 2px #444488; }
        .chartButtonPressed { background-color: #CCFFCC; }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body style="margin:0px;"&gt;
&lt;script type="text/javascript"&gt;

//
// Execute the following initialization code after the web page is loaded
//
JsChartViewer.addEventListener(window, 'load', function() {
    // Update the chart when the view port has changed (eg. when the user zooms in using the mouse)
    var viewer = JsChartViewer.get('&lt;%=viewer.getId()%&gt;');
    viewer.attachHandler("ViewPortChanged", viewer.partialUpdate);

    // Draw track cursor when mouse is moving over plotarea. Hide it when mouse leaves plot area.
    viewer.attachHandler(["MouseMovePlotArea", "TouchStartPlotArea", "TouchMovePlotArea", "PostUpdate",
        "ChartMove"], function(e) {
        this.preventDefault(e);   // Prevent the browser from using touch events for other actions
        trackLineLabel(viewer, viewer.getPlotAreaMouseX());
        viewer.setAutoHide("all", ["MouseOutPlotArea", "TouchEndPlotArea"]);
    });
});

//
// Draw track line with data labels
//
function trackLineLabel(viewer, mouseX)
{
    // Remove all previously drawn tracking object
    viewer.hideObj("all");

    // The chart and its plot area
    var c = viewer.getChart();
    var plotArea = c.getPlotArea();

    // Get the data x-value that is nearest to the mouse, and find its pixel coordinate.
    var xValue = c.getNearestXValue(mouseX);
    var xCoor = c.getXCoor(xValue);

    // Draw a vertical track line at the x-position
    viewer.drawVLine("trackLine", xCoor, plotArea.getTopY(), plotArea.getBottomY(), "black 1px dotted");

    // Draw a label on the x-axis to show the track line position
    viewer.showTextBox("xAxisLabel", xCoor, plotArea.getBottomY() + 4, JsChartViewer.Top,
        c.xAxis().getFormattedLabel(xValue, "mmm dd, yyyy"),
        "font:bold 11px Arial;color:#FFFFFF;background-color:#000000;padding:0px 3px");

    // Iterate through all layers to draw the data labels
    for (var i = 0; i &lt; c.getLayerCount(); ++i)
    {
        var layer = c.getLayerByZ(i);

        // The data array index of the x-value
        var xIndex = layer.getXIndexOf(xValue);

        // Iterate through all the data sets in the layer
        for (var j = 0; j &lt; layer.getDataSetCount(); ++j)
        {
            var dataSet = layer.getDataSetByZ(j);

            // Get the color and position of the data label
            var color = dataSet.getDataColor();
            var yCoor = c.getYCoor(dataSet.getPosition(xIndex), dataSet.getUseYAxis());

            // Draw a track dot with a label next to it for visible data points in the plot area
            if ((yCoor != null) &amp;&amp; (yCoor &gt;= plotArea.getTopY()) &amp;&amp; (yCoor &lt;= plotArea.getBottomY()) &amp;&amp;
                (color != null))
            {
                viewer.showTextBox("dataPoint" + i + "_" + j, xCoor, yCoor, JsChartViewer.Center,
                    viewer.htmlRect(7, 7, color));

                viewer.showTextBox("dataLabel" + i + "_" + j, xCoor + 5, yCoor, JsChartViewer.Left,
                    dataSet.getValue(xIndex).toPrecision(4), "padding:0px 3px;font:bold 11px Arial;" +
                    "background-color:" + color + ";color:#FFFFFF;-webkit-text-size-adjust:100%;");
            }
        }
    }
}

//
// This method is called when the user clicks on the Pointer, Zoom In or Zoom Out buttons
//
function setMouseMode(mode)
{
    var viewer = JsChartViewer.get('&lt;%=viewer.getId()%&gt;');
    if (mode == viewer.getMouseUsage())
        mode = JsChartViewer.Default;

    // Set the button color based on the selected mouse mode
    document.getElementById("scrollButton").className = "chartButton" +
        ((mode  == JsChartViewer.Scroll) ? " chartButtonPressed" : "");
    document.getElementById("zoomInButton").className = "chartButton" +
        ((mode  == JsChartViewer.ZoomIn) ? " chartButtonPressed" : "");
    document.getElementById("zoomOutButton").className = "chartButton" +
        ((mode  == JsChartViewer.ZoomOut) ? " chartButtonPressed" : "");

    // Set the mouse mode
    viewer.setMouseUsage(mode);
}

&lt;/script&gt;
&lt;form method="post" id="ZoomScrollPDF" runat="server"&gt;
&lt;table cellspacing="0" cellpadding="0" style="border:black 1px solid;"&gt;
    &lt;tr&gt;
        &lt;td align="right" colspan="2" style="background:#000088; color:#ffff00; padding:0px 4px 2px 0px;"&gt;
            &lt;a style="color:#FFFF00; font:italic bold 10pt Arial; text-decoration:none" href="http://www.advsofteng.com/"&gt;
                Advanced Software Engineering
            &lt;/a&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr valign="top"&gt;
        &lt;td style="width:130px; background:#c0c0ff;"&gt;
        &lt;div style="width:130px"&gt;
            &lt;!-- The following table is to create 3 cells for 3 buttons to control the mouse usage mode. --&gt;
            &lt;table style="width:100%; padding:0px; border:0px; border-spacing:0px;"&gt;
                &lt;tr&gt;
                    &lt;td class="chartButton" id="scrollButton" onclick="setMouseMode(JsChartViewer.Scroll)"
                        ontouchstart="this.onclick(event); event.preventDefault();"&gt;
                        &lt;img src="scrollew.gif" style="vertical-align:middle" alt="Drag" /&gt;&amp;nbsp;&amp;nbsp;Drag to Scroll
                    &lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td class="chartButton" id="zoomInButton" onclick="setMouseMode(JsChartViewer.ZoomIn)"
                        ontouchstart="this.onclick(event); event.preventDefault();"&gt;
                        &lt;img src="zoomInIcon.gif" style="vertical-align:middle" alt="Zoom In" /&gt;&amp;nbsp;&amp;nbsp;Zoom In
                    &lt;/td&gt;
                &lt;/tr&gt;
                &lt;tr&gt;
                    &lt;td class="chartButton" id="zoomOutButton" onclick="setMouseMode(JsChartViewer.ZoomOut)"
                        ontouchstart="this.onclick(event); event.preventDefault();"&gt;
                        &lt;img src="zoomOutIcon.gif" style="vertical-align:middle" alt="Zoom Out" /&gt;&amp;nbsp;&amp;nbsp;Zoom Out
                    &lt;/td&gt;
                &lt;/tr&gt;
            &lt;/table&gt;
            &lt;div style="margin-top:30px; text-align:center"&gt;
                &lt;input type="button" value="Download Chart" style="width:100%;height:2em"
                    onclick="JsChartViewer.get('&lt;%=viewer.getId()%&gt;').partialUpdateAsAttachment('download=chart');" /&gt;
                &lt;input type="button" value="PDF Report" style="margin-top:5px; width:100%;height:2em"
                    onclick="JsChartViewer.get('&lt;%=viewer.getId()%&gt;').partialUpdateAsAttachment('download=report');" /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;/td&gt;
        &lt;td style="border-left:black 1px solid; padding:10px 5px 0px 5px;"&gt;
            &lt;!-- ****** Here is the chart image ****** --&gt;
            &lt;%=viewer.renderHTML(response)%&gt;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;
&lt;/body&gt;
&lt;/html&gt;</code></div></div><br>
<hr class="separator"><div class="copyright">&copy; 2022 Advanced Software Engineering Limited. All rights reserved.</div></body>
</HTML>
